import { toValue } from "@/utils/helper"
import { useState } from "react"

interface UseSelectOpts<V extends (number | string)> {
  /** @default 'single'' */
  mode?: "single" | "multiple"
  /** @default true */
  treatAll?: boolean
  allValue?: V
  /**
   * @default true
   * @only mode="single"
   */
  unselectable?: boolean
  availableOptions: MaybeGetter<IOption<V>[]>
  /** @default true */
  rollbackSync?: boolean
  syncSelects(selects: V[], isAll: boolean): void
}
export function useOptionSelect<V extends (number | string)>(opts?: UseSelectOpts<V>) {
  const defOpts = <UseSelectOpts<V>>{
    mode: 'single',
    treatAll: true
  };
  const { mode, treatAll, allValue, unselectable= true, availableOptions, rollbackSync = true, syncSelects } = {...defOpts, ...opts };
  const initialValue = toValue(availableOptions).length > 0 ? [toValue(availableOptions)[0].value] : [];
  const [resolvedSelects, setResolvedSelects] = useState<V[]>(getInitialSelects() as V[])
  const [selects, setSelects] = useState<V[]>(getInitialSelects() as V[])
  function apply() {
    setResolvedSelects(cloneSelects(selects))
  }
  function rollback() {
    const nextSelects = cloneSelects(resolvedSelects)
    setSelects(nextSelects)
    if (rollbackSync) {
      // @ts-ignore
      syncSelects(nextSelects, treatAll ? hasSelected(allValue, nextSelects) : nextSelects.length === toValue(availableOptions).length)
    }
  }
  function cloneSelects(s: V[]) {
    return s.concat()
  }
  function getInitialSelects() {
    return (treatAll ? [allValue] : initialValue) as V[]
  }
  function isAllValue(value: unknown) {
    return Object.is(value, allValue)
  }
  function handleItemTap(item: IOption<V>) {
    const exists = hasSelected(item.value)
    if (exists) {
      if (unselectable && mode === "single") return
      const nextSelects = deselect(item.value)
      if (treatAll && nextSelects.length === 0) {
        nextSelects.push(allValue as V)
        setSelects(nextSelects)
      }
      syncSelects(nextSelects, treatAll ? hasSelected(allValue as V, nextSelects) : nextSelects.length === toValue(availableOptions).length)
    } else {
      let nextSelects = cloneSelects(selects)
      if (mode === "single") {
        nextSelects.length = 0
      }
      if (treatAll) {
        const isAll = isAllValue(item.value)
        if (isAll) {
          nextSelects.length = 0
        } else {
          nextSelects = deselect(allValue!, nextSelects)
        }
      }
      nextSelects.push(item.value)
      if (treatAll && nextSelects.length === toValue(availableOptions).length - 1) {
        nextSelects.length = 0
        nextSelects.push(allValue as V)
      }
      setSelects(nextSelects)
      syncSelects(nextSelects, treatAll ? hasSelected(allValue as V, nextSelects) : nextSelects.length === toValue(availableOptions).length)
    }
  }
  function deselect(value: V, source = selects) {
    const index = source.indexOf(value)
    const nextSelects = source.concat()
    if (~index) {
      nextSelects.splice(index, 1)
      setSelects(nextSelects)
    }
    return nextSelects
  }
  function hasSelected(value: V, source?: V[]) {
    return (source ?? selects).includes(value)
  }
  function reset() {
    const nextSelects = getInitialSelects()
    setSelects(nextSelects)
    syncSelects(nextSelects, true)
  }
  function clear() {
    const nextSelects = [] as V[]
    setSelects(nextSelects)
    syncSelects(nextSelects, false)
  }
  /**
   * Select all options manually.
   *
   * Note: Only available when mode is 'mutiple' or treatAll is set.
   */
  function selectAll() {
    if (treatAll) {
      const nextSelects = [allValue] as V[]
      setSelects(nextSelects)
      syncSelects(nextSelects, true)
    } else if (mode === 'multiple') {
      const nextSelects = toValue(availableOptions).map(l => l.value)
      setSelects(nextSelects)
      syncSelects(nextSelects, true)
    }
  }
  function setSelectsValue(value: V[]) {
    setSelects(value)
    apply()
  }
  return {
    selects,
    setSelectsValue,
    handleItemTap,
    reset,
    clear,
    selectAll,
    apply,
    rollback
  }
}
